/*
 * libroxsd - a portable, lightweight XSD validation library
 * Copyright (C) 2012  Emmanuel Deloget
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

#include <stdio.h>
#include <libgen.h>
#include <string.h>
#include <roxml.h>

#include "roxsd.h"

int g_verbosity = 1;

static inline const char *this_version()
{
	return "0.0.1";
}

static void show_usage(char *prgpath)
{
	char *prgname = basename(prgpath);

	/* very unlikely */
	if (!prgname)
		prgname = prgpath;

	fprintf(stdout, "\n");
	fprintf(stdout, "%s: validate an XML file using XML schemas\n", prgname);
	fprintf(stdout, "usage: %s [options] xml-filename\n", prgname);
	fprintf(stdout, "Options are:\n");
	fprintf(stdout, "  --help|-h            : print this text\n");
	fprintf(stdout, "  --xsd|-x             : use this schema (can be used multiple times)\n");
	fprintf(stdout, "  --quiet|-q           : no output\n");
	fprintf(stdout, "  --verbose|-v         : verbose output\n");
	fprintf(stdout, "  --version|-V         : show program and library version\n");
	fprintf(stdout, "\n");
}

static void show_version(char *prgpath)
{
	char *prgname = basename(prgpath);

	/* very unlikely */
	if (!prgname)
		prgname = prgpath;

	fprintf(stdout, "Version information\n%s %s\n%s %s\n", prgname, this_version(), "libroxsd.so", roxsd_version());
}

static void xsd_show_context(node_t *node)
{
	/* show the parent node, one/two attributes (if any) and one/two children (if any) */
	node_t *parent;
	node_t *child;
	node_t *forward_to = NULL;

	fprintf(stderr, "# Context:\n");

	if (roxml_get_type(node) != ROXML_ELM_NODE)
		node = roxml_get_parent(node);
	else
		forward_to = node;

	parent = roxml_get_parent(node);

	if (roxml_get_type(parent) == ROXML_ELM_NODE) {
		char *name = roxml_get_name(parent, NULL, 0);
		fprintf(stderr, "<%s>\n", name);
		roxml_release(name);
	}

	child = roxml_get_chld(parent, NULL, 0);
	if (child) {
		int nc = 0;
		while (child && nc < 2) {
			if (forward_to && child != forward_to) {
				child = roxml_get_next_sibling(child);
				continue;
			}

			char *name = roxml_get_name(child, NULL, 0);

			fprintf(stderr, "  <%s", name);
			roxml_release(name);

			int attr_pos = 0;
			int attr_count = roxml_get_attr_nb(child);

			if (attr_count >= 2)
				attr_count = 2;

			while (attr_pos < attr_count) {
				node_t *attr = roxml_get_attr(child, NULL, attr_pos);
				char *aname = roxml_get_name(attr, NULL, 0);
				char *acontent = roxml_get_content(attr, NULL, 0, NULL);
				fprintf(stderr, " %s='%s'", aname, acontent);
				roxml_release(acontent);
				roxml_release(aname);
				attr = roxml_get_next_sibling(attr);
				++attr_pos;
				if (attr && attr_pos >= 2) {
					fprintf(stderr, " ...");
				}
			}

			fprintf(stderr, ">\n");
			child = roxml_get_next_sibling(child);
			++nc;
			if (child && nc >= 2) {
				fprintf(stderr, "  ...\n");
			}
		}
	}
}

static void xsd_report_error(xsderr_t *err)
{
	int errcode = 0;
	node_t *errnode = NULL;
	const char *errstr = NULL;

	if (roxsd_get_error_desc(err, &errcode, &errstr, &errnode) == 0) {
		if (errnode) {
			char *name;
			char *content;
			fprintf(stderr, "Error on ");
			switch (roxml_get_type(errnode)) {
			case ROXML_ELM_NODE:
				name = roxml_get_name(errnode, NULL, 0);
				fprintf(stderr, "element node <%s>: ", name);
				roxml_release(name);
				break;
			case ROXML_ATTR_NODE:
				name = roxml_get_name(errnode, NULL, 0);
				content = roxml_get_content(errnode, NULL, 0, NULL);
				fprintf(stderr, "attribute node %s='%s': ", name, content);
				roxml_release(content);
				roxml_release(name);
				break;
			default:
				fprintf(stderr, "of unknown type: ");
			}
		} else {
			fprintf(stderr, "Error: ");
		}
		switch (errcode) {
		case ROXSD_ERRCODE_NOT_XSD:
			fprintf(stderr, "the file is not an XML schema\n");
			break;
		case ROXSD_ERRCODE_INVALID_CHILD:
			fprintf(stderr, "the entity is not a valid one at this point. ");
			if (errstr) {
				fprintf(stderr, "It should be one of: %s", errstr);
			};
			fprintf(stderr, "\n");
			if (g_verbosity > 1 && errnode) {
				xsd_show_context(errnode);
			}
			break;
		case ROXSD_ERRCODE_MISSING_NAME:
			fprintf(stderr, "the entity shall have a name='...' attribute.\n");
			if (g_verbosity > 1 && errnode) {
				xsd_show_context(errnode);
			}
			break;
		case ROXSD_ERRCODE_MISSING_TYPE:
			fprintf(stderr, "the entity shall have either a type='...' attribute or it shall define a type in its children.\n");
			if (g_verbosity > 1 && errnode) {
				xsd_show_context(errnode);
			}
			break;
		case ROXSD_ERRCODE_WRONG_TYPE:
			fprintf(stderr, "the entity type is not valid. ");
			if (errstr) {
				fprintf(stderr, "It should be one of: %s", errstr);
			};
			fprintf(stderr, "\n");
			if (g_verbosity > 1 && errnode) {
				xsd_show_context(errnode);
			}
			break;
		case ROXSD_ERRCODE_UNEXPECTED:
			fprintf(stderr, "unexpected entity at this point. ");
			if (errstr) {
				fprintf(stderr, "It should be one of: %s", errstr);
			};
			fprintf(stderr, "\n");
			if (g_verbosity > 1 && errnode) {
				xsd_show_context(errnode);
			}
			break;
		case ROXSD_ERRCODE_INTERNAL:
		default:
			fprintf(stderr, "internal error");
			if (errstr) {
				fprintf(stderr, " with message: %s", errstr);
			}
			fprintf(stderr, "\n");
			break;
		}
	}
}

static void xsd_report_errors(xsdenv_t* env)
{
	xsderr_t *err = roxsd_get_first_error(env);

	while (err) {
		xsd_report_error(err);
		err = roxsd_get_next_error(err);
	}
}

static int xsd_compile_into(xsdenv_t* env, char *xsdpath)
{
	node_t *xmldoc;

	xmldoc = roxml_load_doc(xsdpath);
	if (!xmldoc) {
		return -1;
	}
	if (roxsd_compile_schema(env, xmldoc) < 0) {
		xsd_report_errors(env);
		roxml_close(xmldoc);
		return -1;
	}
	return 0;
}

/** @brief program entry point
 * @param ac argument count
 * @param av argurment values
 * @return 0 if OK, != otherwise
 *
 * We don't use the getopt() API, so someone who wants to windowsify the
 * program will find this easier to do.
 */
int main(int ac, char *av[])
{
	int optind;
	int opt;
	char *input = NULL;
	node_t *xmldoc = NULL;
	xsdenv_t *xsdenv = NULL;

	/* first, set the verbosity level */
	for (optind=1; optind<ac; ++optind) {
		if (strcmp(av[optind], "--quiet") == 0 || strcmp(av[optind], "-q") == 0) {
			--g_verbosity;
		} else if (strcmp(av[optind], "--verbose") == 0 || strcmp(av[optind], "-v") == 0) {
			++g_verbosity;
		}
	}

	if (g_verbosity > 1) {
		fprintf(stdout, "Info: setting verbosity level to %d\n", g_verbosity);
	}

	xsdenv = roxsd_create_env();
	if (!xsdenv) {
		if (g_verbosity >= 1) {
			fprintf(stderr, "Error: cannot create the XSD validation environment\n");
			return EXIT_FAILURE;
		}
	}

	for (optind=1; optind<ac; ++optind) {
		if (strcmp(av[optind], "--help") == 0 || strcmp(av[optind], "-h") == 0) {
			opt = 'h';
		} else if (strcmp(av[optind], "--version") == 0 || strcmp(av[optind], "-V") == 0) {
			opt = 'V';
		} else if (strcmp(av[optind], "--xsd") == 0 || strcmp(av[optind], "-x") == 0) {
			opt = 'x';
			++optind;
		} else if (strcmp(av[optind], "--quiet") == 0 || strcmp(av[optind], "-q") == 0) {
			opt = '-';
		} else if (strcmp(av[optind], "--verbose") == 0 || strcmp(av[optind], "-v") == 0) {
			opt = '-';
		} else {
			opt = '?';
		}

		if (optind >= ac) {
			if (g_verbosity >= 1) {
				fprintf(stderr, "Error: missing argument on option -%c\n", opt);
				show_usage(av[0]);
			}
			goto opt_cleanup;
		}

		switch (opt) {
		case 'h':
			show_usage(av[0]);
			goto opt_cleanup;
		case 'V':
			show_version(av[0]);
			goto opt_cleanup;
		case 'x':
			if (xsd_compile_into(xsdenv, av[optind]) < 0) {
				if (g_verbosity >= 1) {
					fprintf(stderr, "Error: failed to compile schema '%s'\n", av[optind]);
				}
				goto opt_cleanup;
			}
			break;
		case '?':
			if (!input) {
				input = strdup(av[optind]);
			} else {
				if (g_verbosity >= 1) {
					fprintf(stderr, "Error: unknown non-option '%s', and input file is already set to '%s'\n", av[optind], input);
					show_usage(av[0]);
				}
				goto opt_cleanup;
			}
			break;
		case '-':
			/* do nothing */
			break;
		default:
			if (g_verbosity >= 1) {
				fprintf(stderr, "Error: unknown option '%s'\n", av[optind]);
				show_usage(av[0]);
			}
			goto opt_cleanup;
		}
	}

	/*
	 * no input means that we're using the standard input.
	 * no XSD means that we're going to get the XSD from the XML file
	 */
	if (input) {
		xmldoc = roxml_load_doc(input);
	} else {
		/* TODO: not working yet */
		// xmldoc = roxml_load_fd(1);
	}
	if (!xmldoc) {
		if (g_verbosity >= 1) {
			fprintf(stderr, "Error: cannot read input XML file\n");
		}
		goto read_cleanup;
	}

	if (!roxsd_schema_count(xsdenv)) {
		/* TODO: search for an XSD URI in the XML document */
	}

	if (!roxsd_schema_count(xsdenv)) {
		if (g_verbosity >= 1) {
			fprintf(stderr, "Error: no XML schema found\n");
		}
		goto xsd_cleanup;
	}

	/*
	 * at this point, we have everything we need: the schema is loaded,
	 * the XML file is loaded,
	 */

	if (input)
		free(input);

	/* xmldoc is set at this point an cannot be NULL */
	roxml_close(xmldoc);

	/* we don't need this anymore */
	roxsd_release(xsdenv);

	return EXIT_SUCCESS;

xsd_cleanup:
	if (xmldoc)
		roxml_close(xmldoc);

read_cleanup:
	/* nothing special to do */

opt_cleanup:
	if (input)
		free(input);

	roxsd_release(xsdenv);

	return EXIT_FAILURE;
}

